/* 多点触摸应用程序 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <linux/input.h>

/* 用于描述多点触摸(MT)每一个触摸点的信息 */
struct ts_mt
{
    int x; /* x坐标 */
    int y; /* y坐标 */
    int id; /* 对应 ABS_MT_TRACKING_ID */
    int valid; /* 数据有效表标志位(=1 表示触摸点信息发生更新) */
};

/* 一个触摸点的 x坐标和 y坐标 */
struct tp_xy
{
    int x; /* x坐标 */
    int y; /* y坐标 */
};

static int ts_read(const int fd, const int max_slots, struct ts_mt *mt)
{
    struct input_event in_ev;
    static int slot = 0; /* 保存上一个 slot */
    /* 保存上一次的x和y坐标值,假设触摸屏支持的最大出点数不会超过12 */
    static struct tp_xy xy[12] = {0}; 

    /* 对缓冲区初始化操作 */
    memset(mt, 0, max_slots * sizeof(struct ts_mt)); /* 清零 */
    for (int i = 0; i < max_slots; i++) {
        mt[i].id = -2; /* 将 id初始化为-2, id=-1表示触摸点删除, id>=0表示创建 */
    }

    for (;;) {
        /* 读取事件 */
        if (sizeof(struct input_event) !=
              read(fd, &in_ev, sizeof(struct input_event))) {
            perror("read input event failed");
            return -1;
        }

        switch (in_ev.type) {
            case EV_ABS: /* 绝对坐标事件 */
                switch (in_ev.code) {
                    case ABS_MT_SLOT: /* slot */
                        slot = in_ev.value;
                    break;
                    case ABS_MT_POSITION_X: /* x坐标 */
                        xy[slot].x = in_ev.value;
                        mt[slot].valid = 1;
                    break;
                    case ABS_MT_POSITION_Y: /* y坐标 */
                        xy[slot].y = in_ev.value;
                        mt[slot].valid = 1;
                    break;
                    case ABS_MT_TRACKING_ID: /* id */
                        mt[slot].id = in_ev.value;
                        mt[slot].valid = 1;
                    break;
                }
            break;
            case EV_KEY: /* 按键事件对单点触摸应用比较有用 */
            break;
            case EV_SYN: /* 同步事件 */
                if (SYN_REPORT == in_ev.code) {
                    for (int i = 0; i < max_slots; i++) {
                        mt[i].x = xy[i].x;
                        mt[i].y = xy[i].y;
                    }
                }
                return 0;
            break;
        }
    }
}

int main(int argc, char const *argv[])
{
    struct input_absinfo slot;
    struct ts_mt *mt = NULL;
    int fd;
    int max_slots;

    /* 参数校验 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <input_dev>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* 打开文件 */
    fd = open(argv[1], O_RDONLY);
    if (0 > fd) {
        perror("open input device failed");
        exit(EXIT_FAILURE);
    }

    /* 获取触摸屏支持的最大触摸点数 */
    if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot)) {
        perror("ioctl(EVIOCGABS(ABS_MT_SLOT)) failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    max_slots = slot.maximum + 1 - slot.minimum;
    printf("max_slots: %d\n", max_slots);

    /* 申请内存空间并清零 */
    mt = calloc(max_slots, sizeof(struct ts_mt));
    if (NULL == mt) {
        perror("calloc failed");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 读数据 */
    for (;;) {
        if (0 > ts_read(fd, max_slots, mt)) {
            break;
        }

        for (int i = 0; i < max_slots; i++) {
            if (mt[i].valid) { /* 判断每一个触摸点信息是否发生更新 */
                if (0 <= mt[i].id) {
                    printf("slot<%d>,按下(%d, %d)\n", i, mt[i].x, mt[i].y);
                } else if (-1 == mt[i].id) {
                    printf("slot<%d>,松开(%d, %d)\n", i, mt[i].x, mt[i].y);
                } else {
                    printf("slot<%d>,移动(%d, %d)\n", i, mt[i].x, mt[i].y);
                }
            }
        }
    }
    
    /* 关闭设备、退出 */
    close(fd);
    free(mt);
    exit(EXIT_SUCCESS);
}
